Práctica 1.3: Comunicación y sincronización entre procesos
fifotest
Esta práctica persigue que los estudiantes se familiaricen con los dos principales mecanismos de comunicación entre procesos disponibles en Linux: regiones de memoria compartida y tuberías. Además de utilizar estos dos mecanismos (en su variante con nombre) se hará uso de semáforos POSIX con nombre para establecer sincronización entre procesos.
En esta práctica se realizará la modificación de una variante de la implementación del problema productor-consumidor analizada en clase que emplea semáforos con nombre y memoria compartida. Como punto de partida se proporciona el código de dicha solución, que constituye una versión más robusta de la implementación disponible en las transparencias. Esencialmente, después de invocar llamadas al sistema esenciales, esta implementación consulta los valores de retorno de las llamadas para comprobar la existencia de errores y, en su caso, llevar a cabo el tratamiento correspondiente.
En este ejercicio se han de realizar los siguientes cambios en la implementación proporcionada:
Consumer()
shm_open()
man shm_open
En este ejercicio se ha de estudiar la implementación y el comportamiento del programa fifotest introducido en clase. Este programa tiene dos modos de uso que corresponden a las opciones -s (send) y -r (receive) de la línea de comando. Para poder usar este programa es necesario lanzar dos instancias del mismo, cada una en un terminal diferente. Los dos procesos (emisor y receptor) se comunicaran entre sí a traves de un fichero FIFO creado previamente, cuya ruta se ha de pasar con la opción -f del programa.
-s
-r
En el ejemplo mostrado a continuación se ilustra cómo utilizar este programa de forma interactiva, ejecutando las instancias correspondientes de envío y recepción desde distintos terminales:
Terminal 1:
## Creamos fichero FIFO osuser@debian:~/ejercicio2$ mkfifo myfifo ## Lanzamos instancia de envío, e insertamos cadenas de caracteres para enviar al "consumidor" ## Teclear CTRL + D para introducir el fin de fichero en la entrada estándar y acabar la comunicación osuser@debian:~/ejercicio2$ ./fifotest -f myfifo -s Hello How ya doin ...
Terminal 2:
## Lanzamos instancia de recepción, y recibiremos las cadenas desde el otro extremo osuser@debian:~/ejercicio2$ ./fifotest -f myfifo -r Hello How ya doin ...
Este programa puede ejecutarse para realizar envío de forma no interactiva, leyendo datos de un fichero de entrada en lugar de la información introducida con el teclado. Para usar este modo basta utilizar la redirección de entrada del shell como se muestra en el siguiente ejemplo, donde usaremos un fichero de texto ya existente en el directorio:
## Suponemos fichero fifo ya creado ## Lanzamos instancia de envío usando fichero existente como entrada al programa osuser@debian:~/ejercicio2$ ./fifotest -f myfifo -s < test.txt osuser@debian:~/ejercicio2$
## Lanzamos instancia de recepción, y recibiremos el contenido del fichero por el otro extremo osuser@debian:~/ejercicio2$ ./fifotest -f myfifo -r En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor. Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los sábados, lentejas los viernes, algún palomino de añadidura los domingos, consumían las tres partes de su hacienda. El resto della concluían sayo de velarte, calzas de velludo para las fiestas con sus pantuflos de lo mismo, los días de entre semana se honraba con su vellori de lo más fino. Tenía en su casa una ama que pasaba de los cuarenta, y una sobrina que no llegaba a los veinte, y un mozo de campo y plaza, que así ensillaba el rocín como tomaba la podadera. Frisaba la edad de nuestro hidalgo con los cincuenta años, era de complexión recia, seco de carnes, enjuto de rostro; gran madrugador y amigo de la caza. Quieren decir que tenía el sobrenombre de Quijada o Quesada (que en esto hay alguna diferencia en los autores que deste caso escriben), aunque por conjeturas verosímiles se deja entender que se llama Quijana; pero esto importa poco a nuestro cuento; basta que en la narración dél no se salga un punto de la verdad. osuser@debian:~/ejercicio2$
Tras estudiar la implementación del programa y ejecutarlo como se muestra en los ejemplos especificados, contesta a las siguientes preguntas:
gdb
strace
strace ./fifotest -f myfifo -s
'\n'
Implementar el programa de usuario chat.c, un sencillo chat que hará uso de ficheros FIFO y múltiples hilos POSIX para la comunicación. El modo de uso del programa será el siguiente:
chat.c
./chat <usuario> <ruta-fifo-envío> <ruta-fifo-recepción>
donde usuario es una cadena de caracteres arbitraria que representa el nombre del usuario que se “conecta” al chat. El programa usará un FIFO para enviar mensajes al otro usuario del chat y otro FIFO para recibir los mensajes. Las rutas de ambos ficheros FIFO –que han de estar ya creados– se ha de pasar como argumento, como consta en el modo de uso.
usuario
Para poder usar el chat será necesario lanzar dos instancias de dicho programa en distintos terminales del siguiente modo:
$ ./chat <usuario1> <ruta-fifo1> <ruta-fifo2>
$ ./chat <usuario2> <ruta-fifo2> <ruta-fifo1>
Es decir, en ambas instancias del programa se pasará la ruta del mismo par de ficheros FIFOs, pero en distinto orden. Al fin y al cabo, el FIFO de envío en una de las instancias del programa será el FIFO de recepción de mensajes para la otra instancia del programa.
Para la correcta utilización de los FIFOs en el modo de uso habitual de estos (modo bloqueante), el programa chat.c creará dos hilos (emisor y receptor) usando pthread_create():
pthread_create()
En la siguiente figura se muestra un ejemplo de ejecución del programa chat.c, así como un diagrama que ilustra la interacción entre los dos hilos (emisor y receptor) presentes en las dos instancias del programa. En el ejemplo, estos hilos se comunican usando los ficheros FIFO /tmp/fifo0 y /tmp/fifo1 previamente creados:
/tmp/fifo0
/tmp/fifo1
Por simplicidad en la implementación, los datos transferidos a través de cada FIFO se representarán mediante el tipo de datos struct chat_message, donde se recogen distintos tipos de mensajes:
struct chat_message
#define MAX_CHARS_MSG 128 typedef enum { NORMAL_MSG, /* Mensaje para transferir lineas de la conversacion entre ambos usuarios del chat */ USERNAME_MSG, /* Tipo de mensaje reservado para enviar el nombre de usuario al otro extremo*/ END_MSG /* Tipo de mensaje que se envía por el FIFO cuando un extremo finaliza la comunicación */ }message_type_t; struct chat_message{ char contenido[MAX_CHARS_MSG]; //Cadena de caracteres (acabada en '\0) message_type_t type; };
El comportamiento del hilo emisor y receptor de mensajes se resume a continuación, indicando también el uso de los tres tipos de mensajes (message_type_t) durante el protocolo de comunicación:
message_type_t
Comportamiento hilo emisor de mensajes
USERNAME_MSG
NORMAL_MESSAGE
CTRL+D
END_MESSAGE
Comportamiento hilo receptor de mensajes
Abre FIFO de recepción en modo lectura
Procesa mensaje de nombre de usuario (USERNAME_MSG) leyendo del FIFO de recepción
Procesa resto de mensajes hasta fin de fichero en el FIFO de recepción, error de lectura o recepción de END_MESSAGE
Al recibir un mensaje normal, imprime el campo content por pantalla con el siguiente formato:
content
<nombre_usuario_emisor> dice: <contenido>
Cierra FIFO de recepción y sale